﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Hosting;
using System.IO;
using System.CodeDom.Compiler;

namespace MVCPlugin.Utility
{
    public enum DotNetFrameworkVersion
    {
        V4_5,
        V4_0,
        V3_5,
        V3_0,
        V2_0,
        V1_1
    }

    public class ProjectCompilerResult
    {
        public bool Success { get; set; }
        public ProjectFileDescriptor ProjectFileDescriptor { get; set; }
        public string ErrorMessage { get; set; }
    }

    public static class ProjectCompiler
    {
        private static ILogger Logger = new ConsoleLogger();
        public static ProjectCompilerResult Compile(string projectFilePath, DotNetFrameworkVersion supportedVersion)
        {
            if (projectFilePath == null || projectFilePath.ToString().Length == 0)
            {
                throw new ArgumentNullException("projectFilePath");
            }
            string version = "v4.5";
            switch (supportedVersion)
            {
                case DotNetFrameworkVersion.V1_1:
                    version = "v1.1";
                    break;
                case DotNetFrameworkVersion.V2_0:
                    version = "v2.0";
                    break;
                case DotNetFrameworkVersion.V3_0:
                    version = "v3.0";
                    break;
                case DotNetFrameworkVersion.V3_5:
                    version = "v3.5";
                    break;
                case DotNetFrameworkVersion.V4_0:
                    version = "v4.0";
                    break;
                case DotNetFrameworkVersion.V4_5:
                    version = "v4.5";
                    break;
                default:
                    break;
            }
            if (projectFilePath.Length > 0 && projectFilePath[0] == '~')
            {
                projectFilePath = HostingEnvironment.MapPath(projectFilePath);
            }
            else
            {
                string r = Path.GetPathRoot(projectFilePath);
                if (string.IsNullOrWhiteSpace(r))
                {
                    projectFilePath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, projectFilePath);
                }
            }
            projectFilePath = projectFilePath.Trim();
            var doc = CacheHelper.GetWithLocalCache<ProjectFileDescriptor>(projectFilePath, () => ProjectFileParser.Parse(projectFilePath), projectFilePath);
            if (doc.TargetFrameworkVersion != version)
            {
                return new ProjectCompilerResult()
                {
                    Success = false,
                    ErrorMessage = string.Format("Only supports .net framework {0}, but the project is on '{1}'.", version, doc.TargetFrameworkVersion),
                    ProjectFileDescriptor = doc
                };
            }
            if (doc.OutputType.Equals("Library", StringComparison.InvariantCultureIgnoreCase) == false)
            {
                return new ProjectCompilerResult()
                {
                    Success = false,
                    ErrorMessage = string.Format("Only supports to compile the project for creating a class library(.dll). But the output type of the project to compile is '{0}'.", doc.OutputType),
                    ProjectFileDescriptor = doc
                };
            }
            string baseFolder = Path.GetDirectoryName(projectFilePath);
            try
            {
                var results = Compile(baseFolder, doc);
                StringBuilder sb = new StringBuilder();
                if (results.Errors != null && results.Errors.Count > 0)
                {
                    foreach (CompilerError error in results.Errors)
                    {
                        if (error.IsWarning)
                        {
                            continue;
                        }
                        string line = string.Format("ERROR '{0}'({1},{2}): [{3}] {4}", error.FileName, error.Line, error.Column, error.ErrorNumber, error.ErrorText);
                        sb.AppendLine(line);
                    }
                }
                bool success = sb.Length <= 0;
                if (success)
                {
                    // copy othe lib dll files...
                    string binFolder = Path.Combine(baseFolder, "bin");
                    foreach (var reference in doc.References)
                    {
                        if (reference.ReferenceType == ReferenceType.Project || string.IsNullOrWhiteSpace(reference.Path)
                            || reference.Path.StartsWith(@"..\..\bin\", StringComparison.InvariantCultureIgnoreCase) || reference.Path.StartsWith(@"..\..\" + Application.BinFolderName + @"\", StringComparison.InvariantCultureIgnoreCase))
                        {
                            continue;
                        }
                        string dllPath = Path.Combine(baseFolder, reference.Path);
                        string targetPath = Path.Combine(binFolder, Path.GetFileName(dllPath));
                        File.Copy(dllPath, targetPath, true);
                    }
                }
                return new ProjectCompilerResult()
                {
                    Success = success,
                    ErrorMessage = sb.ToString(),
                    ProjectFileDescriptor = doc
                };
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                return new ProjectCompilerResult()
                {
                    Success = false,
                    ErrorMessage = ex.Message,
                    ProjectFileDescriptor = doc
                };
            }
        }

        private static CompilerResults Compile(string baseFolder, ProjectFileDescriptor descriptor)
        {
            string binFolder = Path.Combine(baseFolder, "bin");
            if (Directory.Exists(binFolder) == false)
            {
                Directory.CreateDirectory(binFolder);
            }
            CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
            CompilerParameters parameters = new CompilerParameters();
            parameters.GenerateInMemory = false;
            parameters.OutputAssembly = Path.Combine(binFolder, descriptor.AssemblyName + ".dll");
            parameters.CompilerOptions = "/optimize";
            parameters.IncludeDebugInformation = false;
            foreach (var reference in descriptor.References)
            {
                parameters.ReferencedAssemblies.Add(string.IsNullOrWhiteSpace(reference.Path) ? reference.FullName + ".dll" : Path.Combine(baseFolder, reference.Path));
            }
            List<string> sf = new List<string>();
            foreach (var ds in descriptor.SourceFilenames)
            {
                sf.Add(Path.Combine(baseFolder, ds));
            }
            return codeProvider.CompileAssemblyFromFile(parameters, sf.ToArray());
        }
    }
}
